/*	videoID.cpp
 *
 *	This class tries to gather as much information about the videocard(s) in
 *	the system as it can. Currently it only uses the windows registry to get
 *	this information, although I am considering adding support for OpenGL
 *	and DirectX. Will have to see about this.
 *
 *	This code is mostly the result of searching my own registry for anything
 *	that looks interesting. As such, the code is biased heavily towards ATI
 *	cards. It would be nice if I could get a programmer with an NVIDIA card
 *	working on this. I have already found someone on IRC who seems willing to
 *	do extensive testing.
 *
 *	This file has not been completed commented yet.
 *
 *	Platform Indepedent:		No.
 *
 *
 *	Created:
 *			R. Appleton			14 August 2005
 */
#include <iostream>			// Only needed for the logging
#include <sstream>
#include "videoID.hpp"
#include "regkey.h"

//	==========================================================================
//	Defines for logging
//	==========================================================================
// Uncomment the following to disable logging.
//#define NO_LOGGING

#ifdef NO_LOGGING
	#define LOG(a) 
#else
	#define LOG(a) a
#endif


/*
//	==========================================================================
//	std::string GetAllKeysAndValues()
// 
//	Enumerates all keys of the give type under the entered key. Used while
//	while debugging, won't use for final version probably, hence commented 
//	out.
//
//	In:
//			RegKey			The registry key to find the subkeys of.
//	Out:
//			std::string		All the subkeys of 'key'.
//	==========================================================================
std::string GetAllKeysAndValues( RegKey key)
{
	std::stringstream str;
	RegValueForwardIterator i(key), e;
	for (; i != e; ++i) {
		LPCTSTR name = (*i).first;
		RegValue value = (*i).second;
		switch(value.Type)
		{
		case REG_SZ:
			str << name << ": " << value.operator LPCTSTR() << std::endl;
			break;
		case REG_DWORD:
			//str << name << ": " << value.operator DWORD() << std::endl;
			break;
		case REG_QWORD:
			//str << name << ": " << value.operator QWORD() << std::endl;
			break;
		default: break;
		}
	}

	return str.str();
}
*/

//	==========================================================================
//	bool VideoID::ReadDeviceMap()
// 
//	Search through the registry key HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\VIDEO
//	for any and all video devices.
//
//	HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\VIDEO should have a number of
//	\Device\VideoX keys, where X is [0..MaxObjectNumber]
//	==========================================================================
bool VideoID::ReadDeviceMap( void )
{
	// Open the key
	LOG(std::cout << "Opening Devicemap registry key." << std::endl);
	RegKey key(HKEY_LOCAL_MACHINE);
	key.Open("HARDWARE\\DEVICEMAP\\VIDEO");

	// The number of video devices
	RegValue value = key["MaxObjectNumber"];
	if(!value.IsValid())
		return false;	// Apparently there was no MaxObjectNumber, huh?

	// Store the number of objects
	unsigned int uMaxVideoDevices = static_cast<DWORD>(value);
	LOG(std::cout << "Going to read " << uMaxVideoDevices << " \\Device\\Video's" << std::endl);

	std::stringstream buffer;
	for(unsigned int uCurrentVideo=0; uCurrentVideo<=uMaxVideoDevices; ++uCurrentVideo)
	{
		buffer.str("");
		buffer << "\\Device\\Video" << uCurrentVideo;

		LOG(std::cout << "Getting value for " << buffer.str() << std::endl);
		key.Open("HARDWARE\\DEVICEMAP\\VIDEO");
		value = key[buffer.str().c_str()];			// We can recycle the RegValue value, we don't need it anymore.

		if(!value.IsValid())
		{
			LOG(std::cout << "Error, it doesn't exist." << std::endl);
			return false;	// Huh, this key should really exist.
		}

		std::string sDeviceKey(static_cast<const char*>(value));

		// Remove the first part since that is actually HKEY_LOCAL_MACHINE
		sDeviceKey = sDeviceKey.substr(strlen("\\Registry\\Machine\\"));

		// Open the given key
		LOG(std::cout << "Opening registry key " << sDeviceKey << std::endl);
		key.Open(sDeviceKey.c_str());
		ReadSingleDeviceFromDeviceMap(key);
	}

	return true;
}

//	==========================================================================
//	bool VideoID::ReadSingleDeviceFromDeviceMap()
// 
//	Follow the device from HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\VIDEO\Device\VideoX
//
//	==========================================================================
void VideoID::ReadSingleDeviceFromDeviceMap( RegKey& key )
{
	LOG(std::cout << "ReadSingleDeviceFromDeviceMap called" << std::endl);
	
	RegValue deviceDescription = key["Device Description"];
	if(deviceDescription.IsValid())
	{
		// The structure that will be filled in
		VideoDevice videocard;

		// The description is valid, so we have at least something
		videocard.m_Description			= static_cast<const char*>(deviceDescription);

		// Filter out strange descriptions that we're not interested in.
		// More may be encountered as we test, but these are the only ones I've seen so far.
		if(	videocard.m_Description!="NetMeeting driver" &&
			videocard.m_Description!="RDPDD Chained DD"
		)
		{	// It looks like this really is a videodevice
			LOG(std::cout << "Videocard found" << std::endl);

			// Check if this is actually the second output on a primary card
			std::size_t secPos = videocard.m_Description.find("Secondary");
			if(secPos!=std::string::npos && m_VideoDevices.size()>0)
			{	// This is a secondary device, and we already have a device.
				// We should already have found the corresponding primary device.
				// Search through the devices for it.
				if( FindCorrespondingPrimaryDevice(videocard.m_Description) )
					return;	// Yep we did, return without doing anything else.	
			}
			// Apparently we couldn't find any corresponding primary device
			if(videocard.m_Description.find("ATI"))
			{
				RegValue releaseVersion				= key["ReleaseVersion"];
				if(releaseVersion.IsValid())
					videocard.m_Driver.m_Version	= static_cast<const char*>(releaseVersion);
				else
					videocard.m_Driver.m_Version	= "Error, no 'ReleaseVersion' key";
			}

			// See if we can deduce the manufacturer from the description string
			if( videocard.m_Description.find("RADEON")!=std::string::npos )
			{
				videocard.m_Manufacturer		= "ATI";
			}
			else if(videocard.m_Description.find("NVIDIA")!=std::string::npos)
			{	
				videocard.m_Manufacturer		= "NVIDIA";
			}
			
			// Store this device
			m_VideoDevices.push_back(videocard);
		}
	}
}

//	==========================================================================
//	bool VideoID::FindCorrespondingPrimaryDevice()
// 
//	Find a corresponding primary device for the given secondary device.
//
//	Out:
//			true		Found a primary device.
//			false		Couldn't find a primary device.
//	==========================================================================
bool VideoID::FindCorrespondingPrimaryDevice( std::string secondary )
{
	// Create possible primary string
	std::vector<VideoDevice>::const_iterator vidDeviceIt(m_VideoDevices.begin());
	std::vector<VideoDevice>::const_iterator vidDeviceItEnd(m_VideoDevices.end());
	while(vidDeviceIt!=vidDeviceItEnd)
	{
		if(secondary.find(vidDeviceIt->m_Description)!=std::string::npos)
			return true;	// Yep this is the primary device, don't do anything else

		// Nope, this isn't it, try the next
		++vidDeviceIt;
	}
	// No, no similar primary device

	return false;
}

//	==========================================================================
//	bool VideoID::GetInfo()
// 
//	Simply calls all the other functions to do the work.
//
//	Out:
//			true		Succesfully gathered all the data.
//			false		Some error.
//	==========================================================================
bool VideoID::GetInfo( void )
{
	// Empty all current devices
	m_VideoDevices.clear();

	return ReadDeviceMap();
}

//	==========================================================================
//	bool VideoID::GetInfo2()
// 
//	Second method of gathering info on the video devices. This involves quite
//	a trip through the registry, and I'm not sure is this actually gets any
//	info on non-ATI cards (or even on all ATI cards).
//
//	Out:
//			true		If everything went okay, which it should. In the worst
//						case, we gathered no info.
//			false		???
//	==========================================================================
bool VideoID::GetInfo2( void )
{
	// Empty all current devices
	m_VideoDevices.clear();

	RegKey key(HKEY_LOCAL_MACHINE);
	key.Open("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Control Panel\\Settings\\Video");

	RegKeyForwardIterator i(key), e;
	for (; i != e; ++i) 
	{	
		// The subkeys should be like this: PCI:VEN_1002&DEV_4150&SUBSYS_41501002&REV_00
		std::string sEnumKey(*i);
		sEnumKey[sEnumKey.find(":")] = '\\';
		sEnumKey = "SYSTEM\\CurrentControlSet\\Enum\\" + sEnumKey;

		// Go to the enum folder
		RegKey enumKey(HKEY_LOCAL_MACHINE);
		LONG result = enumKey.Open(sEnumKey.c_str(), KEY_READ);

		if(result==ERROR_SUCCESS)
		{
			// Find the single subkey
			RegKeyForwardIterator enumI(enumKey), enumEnd;
			for (; enumI != enumEnd; ++enumI) 
			{
				sEnumKey.append("\\");
				sEnumKey.append(*enumI);

				// Go to the PCI object
				RegKey pciKey(HKEY_LOCAL_MACHINE);
				pciKey.Open(sEnumKey.c_str(), KEY_READ);
				
				VideoDevice videocard;
				
				RegValue manufacturer			= pciKey["Mfg"];
				if(manufacturer.IsValid())
					videocard.m_Manufacturer	= static_cast<const char*>(manufacturer);
				else
					videocard.m_Manufacturer	= "Error, no 'Mfg' key";
				RegValue deviceDescription		= pciKey["DeviceDesc"];
				if(deviceDescription.IsValid())
					videocard.m_Description		= static_cast<const char*>(deviceDescription);
				else
					videocard.m_Description		= "Error, no 'DeviceDesc' key";
					
				RegValue driver					= pciKey["Driver"];
				if(driver.IsValid())
				{
					std::string sDriverKey		= "SYSTEM\\ControlSet001\\Control\\Class\\";
					sDriverKey.append(static_cast<const char*>(driver));

					// Go to the driver key
					RegKey driverKey(HKEY_LOCAL_MACHINE);
					if(driverKey.Open(sDriverKey.c_str(), KEY_READ)==ERROR_SUCCESS)
					{
						ReadDriverInfo2(videocard, driverKey);
					}
					else
					{
						videocard.m_Driver.m_Provider = "Error, couldn't find '";
						videocard.m_Driver.m_Provider.append(sDriverKey);
						videocard.m_Driver.m_Provider.append("' key");
					}
				}
				else
					videocard.m_Driver.m_Version	= "Error, no 'Driver' key";
								
				m_VideoDevices.push_back(videocard);
			}
		}
		else // !(result==ERROR_SUCCESS)
		{
			char buf[512];
			FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, result, 0, buf, 512, 0);
			//std::cout << buf << std::endl;
		}
	}

	return true;
}

//	==========================================================================
//	void VideoID::ReadDriverInfo2()
// 
//	In:
//			RegKey			The registry key to gather the data from.
//	Out:
//			VideoDevice		All info is stored in here.
//	==========================================================================
void VideoID::ReadDriverInfo2( VideoDevice& videocard, RegKey& driverKey )
{
	// Driver version
	RegValue driverVersion				= driverKey["DriverVersion"];
	if(driverVersion.IsValid())
		videocard.m_Driver.m_Version	= static_cast<const char*>(driverVersion);
	else
		videocard.m_Driver.m_Version	= "Error, no 'DriverVersion' key";
	
	// Driver date
	RegValue driverDate					= driverKey["DriverDate"];
	if(driverDate.IsValid())
		videocard.m_Driver.m_Date		= static_cast<const char*>(driverDate);
	else
		videocard.m_Driver.m_Date		= "Error, no 'DriverDate' key";

	// Driver provider
	RegValue driverProvider				= driverKey["ProviderName"];
	if(driverProvider.IsValid())
		videocard.m_Driver.m_Provider	= static_cast<const char*>(driverProvider);
	else
		videocard.m_Driver.m_Provider	= "Error, no 'ProviderName' key";

	std::cout << "provider " << videocard.m_Driver.m_Provider << std::endl;
	// Try for specific vendor's drivers
	if(videocard.m_Driver.m_Provider.find("ATI")!=std::string::npos)
	{
		ReadATIInfo(videocard, driverKey);
	}
	else if(videocard.m_Driver.m_Provider.find("NVIDIA")!=std::string::npos)
	{
		ReadNVIDIAInfo( videocard );
	}
	// ToDo: add other vendors here.

}

//	==========================================================================
//	void VideoID::ReadATIInfo()
// 
//	Tries to find more ATI specific info.
//
//	In:
//			RegKey			The registry key to gather the data from.
//	Out:
//			VideoDevice		All info is stored in here.
//	==========================================================================
void VideoID::ReadATIInfo( VideoDevice& videocard, RegKey& key )
{	
	// Maybe it has a catalyst driver in '\Settings\Catalyst_Version'
	std::string sSettings = key.Path();
	sSettings.append("\\Settings");
	RegKey settingsKey(HKEY_LOCAL_MACHINE);
	if(settingsKey.Open(sSettings.c_str(), KEY_READ)==ERROR_SUCCESS)
	{
		RegValue driverVersion				= settingsKey["Catalyst_Version"];
		if(driverVersion.IsValid())
		{
			videocard.m_Driver.m_Version	= "Catalyst ";
			std::string ver					= static_cast<const char*>(driverVersion);
			std::size_t p = 0;
			while (ver[p]=='0')
				++p;
			videocard.m_Driver.m_Version.append(ver.substr(p));
		}
	}

	Hardware& hardware = videocard.m_Hardware;

	// HKEY_LOCAL_MACHINE\SOFTWARE\ATI Technologies\CDS\0000\0\Memory
	RegKey memoryKey(HKEY_LOCAL_MACHINE);
	if(memoryKey.Open("SOFTWARE\\ATI Technologies\\CDS\\0000\\0\\Memory", KEY_READ)==ERROR_SUCCESS)
	{
		RegValue memoryValue;
		std::string sMemory;

		// Size
		memoryValue							= memoryKey["Size"];
		if(memoryValue.IsValid())
		{
			sMemory							= static_cast<const char*>(memoryValue);
			hardware.m_MemorySizeMB			= atoi(sMemory.substr(0, sMemory.find(" MB")).c_str());
		}

		// Type of memory
		memoryValue							= memoryKey["Type"];
		if(memoryValue.IsValid())
		{
			hardware.m_MemoryType			= static_cast<const char*>(memoryValue);
			hardware.m_MemoryType = hardware.m_MemoryType.substr(0, hardware.m_MemoryType.find(" "));
		}
	}

	// HKEY_LOCAL_MACHINE\SOFTWARE\ATI Technologies\CDS\0000\0\ASIC
	RegKey asicKey(HKEY_LOCAL_MACHINE);
	if(asicKey.Open("SOFTWARE\\ATI Technologies\\CDS\\0000\\0\\ASIC", KEY_READ)==ERROR_SUCCESS)
	{
		RegValue asicValue;
		std::string sASIC;

		// Chip ID
		asicValue							= asicKey["Chip ID"];
		if(asicValue.IsValid())
		{
			hardware.m_ChipType				= static_cast<const char*>(asicValue);
			hardware.m_ChipType = hardware.m_ChipType.substr(0, hardware.m_ChipType.find(" ("));
		}
	}

	// HKEY_LOCAL_MACHINE\SOFTWARE\ATI Technologies\CDS\0000\0\PCI Config
	RegKey pciKey(HKEY_LOCAL_MACHINE);
	if(pciKey.Open("SOFTWARE\\ATI Technologies\\CDS\\0000\\0\\PCI Config", KEY_READ)==ERROR_SUCCESS)
	{
		RegValue pciValue;
		std::string sPCI;

		// Chip ID
		pciValue							= pciKey["AGP Transfer Rate"];
		if(pciValue.IsValid())
		{
			hardware.m_AGPRate				= static_cast<const char*>(pciValue);
			hardware.m_AGPRate = hardware.m_AGPRate.substr(0, hardware.m_AGPRate.find(" ("));
		}
	}
}

//	==========================================================================
//	void VideoID::ReadNVIDIAInfo()
// 
//	Tries to find more NVIDIA specific info.
//
//	In:
//			RegKey			The registry key to gather the data from.
//	Out:
//			VideoDevice		All info is stored in here.
//	==========================================================================
void VideoID::ReadNVIDIAInfo( VideoDevice& videocard )
{	
	// Maybe it has a catalyst driver in '\Settings\Catalyst_Version'
	RegKey settingsKey(HKEY_LOCAL_MACHINE);
	std::string deviceLocation;
	if(settingsKey.Open("HARDWARE\\DEVICEMAP\\VIDEO", KEY_READ)==ERROR_SUCCESS)
	{
		RegValue device				= settingsKey["\\Device\\Video0"];
		if(device.IsValid())
		{
			deviceLocation			= static_cast<const char*>(device);
		}
	}

	Hardware& hardware = videocard.m_Hardware;

	RegKey memoryKey(HKEY_LOCAL_MACHINE);
	if(memoryKey.Open(deviceLocation.substr(deviceLocation.find("\System")).c_str(), KEY_READ)==ERROR_SUCCESS)
	{
		RegValue memoryValue;
		std::string sMemory;
		// Size
		memoryValue							= memoryKey["HardwareInformation.MemorySize"];
		if(memoryValue.IsValid())
		{
			sMemory							= static_cast<const char*>(memoryValue);
			hardware.m_MemorySizeMB			= *static_cast<DWORD*>(memoryValue.Pointer()) / (1024*1024);
			
		}

		// Type of memory
		char adapter[200];
		memoryValue							= memoryKey["HardwareInformation.AdapterString"];
		if(memoryValue.IsValid())
		{
			wchar_t *unicodeString			= (wchar_t*)memoryValue.Pointer();
			WideCharToMultiByte(CP_ACP, 0, unicodeString, -1, adapter, 200, 0, 0);
			hardware.nvidia.adapter			= adapter;
		}

		memoryValue							= memoryKey["HardwareInformation.BiosString"];
		if(memoryValue.IsValid())
		{
			wchar_t *unicodeString			= (wchar_t*)memoryValue.Pointer();
			WideCharToMultiByte(CP_ACP, 0, unicodeString, -1, adapter, 200, 0, 0);
			hardware.nvidia.bios			= adapter;
		}

		memoryValue							= memoryKey["HardwareInformation.ChipType"];
		if(memoryValue.IsValid())
		{
			wchar_t *unicodeString			= (wchar_t*)memoryValue.Pointer();
			WideCharToMultiByte(CP_ACP, 0, unicodeString, -1, adapter, 200, 0, 0);
			hardware.nvidia.chiptype		= adapter;
		}

		memoryValue							= memoryKey["HardwareInformation.DacType"];
		if(memoryValue.IsValid())
		{
			wchar_t *unicodeString			= (wchar_t*)memoryValue.Pointer();
			WideCharToMultiByte(CP_ACP, 0, unicodeString, -1, adapter, 200, 0, 0);
			hardware.nvidia.dactype			= adapter;
		}
	}
}


//------------------------------------------------------------------------
// Copyright (c) 2005 Rick Appleton
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would
//    be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
//    be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
//    distribution.
//
// Rick Appleton
// www.daedalus-development.net
//------------------------------------------------------------------------
